package com.lemon.exam.service.impl;

import com.lemon.exam.common.constant.ApiConst;
import com.lemon.exam.common.constant.RedisKeyConst;
import com.lemon.exam.common.crud.BaseServiceImpl;
import com.lemon.exam.common.entity.system.SysMethod;
import com.lemon.exam.common.entity.system.SysWeb;
import com.lemon.exam.common.enums.StatusEnum;
import com.lemon.exam.common.exception.CustomException;
import com.lemon.exam.common.redis.RedisService;
import com.lemon.exam.common.util.SecurityUtil;
import com.lemon.exam.entity.dto.RoleMenuResourceDTO;
import com.lemon.exam.entity.po.RolePO;
import com.lemon.exam.entity.vo.MenuResourceVO;
import com.lemon.exam.entity.vo.MenuVO;
import com.lemon.exam.entity.vo.ResourceVO;
import com.lemon.exam.entity.vo.UserVO;
import com.lemon.exam.repository.RoleRepository;
import com.lemon.exam.service.IMenuService;
import com.lemon.exam.service.IRoleMenuResourceService;
import com.lemon.exam.service.IRoleService;
import com.lemon.exam.service.IUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.util.*;
import java.util.stream.Collectors;

import static com.lemon.exam.common.constant.ApiConst.UPDATE_SUCCESS;

/**
 * 角色表 服务实现类
 *
 * @author Lemon
 * @since 2025/04/04 14:23:22
 */
@Slf4j
@Service
public class RoleServiceImpl extends BaseServiceImpl<RolePO, Long, RoleRepository> implements IRoleService {
    @Resource
    private IUserService userService;
    @Resource
    private IMenuService menuService;
    @Resource
    private IRoleMenuResourceService roleMenuResourceService;
    @Resource
    private RedisService redisService;

    @Override
    public Mono<String> insert(RolePO param) {
        return Mono.zip(
                SecurityUtil.getUsername(),
                super.exist("name", param.getName()),
                super.exist("role_key", param.getRoleKey())
        ).flatMap(tuple -> {
            if (tuple.getT2()) {
                return Mono.error(new CustomException(402, "角色名称已存在！"));
            } else if (tuple.getT3()) {
                return Mono.error(new CustomException(402, "权限字符已存在！"));
            } else {
                param.setStatus(StatusEnum.ENABLE.getValue());
                param.setCreateBy(tuple.getT1());
                return super.insert(param);
            }
        });
    }

    @Override
    public Mono<String> update(RolePO param) {
        return Mono.zip(
                        SecurityUtil.getUsername(),
                        super.exist("name", param.getName(), param.getId()),
                        super.exist("role_key", param.getRoleKey(), param.getId())
                ).flatMap(tuple -> {
                    if (tuple.getT2()) {
                        return Mono.error(new CustomException(402, "角色名称已存在！"));
                    } else if (tuple.getT3()) {
                        return Mono.error(new CustomException(402, "权限字符已存在！"));
                    } else {
                        param.setUpdateBy(tuple.getT1());
                        return super.update(param);
                    }
                })
                .onErrorResume(CustomException.class, Mono::error);
    }

    @Override
    public Flux<UserVO> findUser(Long roleId) {
        return userService.findUserByRoleId(roleId);
    }

    @Override
    public Flux<MenuResourceVO> findMenuResource(Long roleId) {
        return Mono.zip(
                menuService.findAllByStatus().collectList().subscribeOn(Schedulers.parallel()),
                roleMenuResourceService.findByRoleId(roleId).subscribeOn(Schedulers.parallel()),
                this.getMenuResourceMap()
        ).flatMapMany(tuple -> Flux.fromIterable(buildTreeMenu(
                tuple.getT1(),
                tuple.getT3().getT2(),
                tuple.getT2().keySet(),
                tuple.getT2(),
                tuple.getT3().getT1()
        )));
    }

    /**
     * 从Redis中获取菜单资源
     *
     * @return
     */
    private Mono<Tuple2<Map<String, Map<String, String>>, Map<String, String>>> getMenuResourceMap() {
        return redisService.set().get(RedisKeyConst.SYSTEM_INTERFACE, SysWeb.class).collectList()
                .map(list -> Tuples.of(
                        /**
                         * Redis中的菜单资源
                         * key  -> 菜单path
                         * value -> 资源路径 - 资源名称
                         */
                        list.stream().collect(Collectors.toMap(
                                SysWeb::path,
                                entry -> entry.methods().stream().collect(Collectors.toMap(SysMethod::path, SysMethod::name))
                        )),
                        /**
                         * 获取 Redis 中所有的路径和名称
                         * key  -> 菜单path
                         * value -> 资源名称
                         */
                        list.stream().collect(Collectors.toMap(SysWeb::path, SysWeb::name))
                ))
                .subscribeOn(Schedulers.parallel());
    }

    /**
     * 构建菜单资源
     *
     * @param menuList
     * @param pathName
     * @param menuIdList
     * @param menuResourceMap
     * @param allMenuResourceMap
     * @return
     */
    private List<MenuResourceVO> buildTreeMenu(List<MenuVO> menuList, Map<String, String> pathName, Set<Long> menuIdList, Map<Long, List<String>> menuResourceMap, Map<String, Map<String, String>> allMenuResourceMap) {
        List<MenuResourceVO> menus = menuList.stream().peek(menu -> {
            if (StringUtils.hasText(menu.getPath()) && !StringUtils.hasText(menu.getName())) {
                menu.setName(pathName.get(menu.getPath()));
            }
        }).map(menu -> {
            boolean contains = menuIdList.contains(menu.getId());
            List<ResourceVO> resourceList = new ArrayList<>();
            if (StringUtils.hasText(menu.getPath())) {
                List<String> menuResourceList = menuResourceMap.getOrDefault(menu.getId(), Collections.emptyList())
                        .stream()
                        .map(item -> menu.getPath() + item)
                        .toList();
                resourceList = allMenuResourceMap.getOrDefault(menu.getPath(), Collections.emptyMap())
                        .entrySet()
                        .stream()
                        .map(entry -> new ResourceVO(
                                entry.getKey().replaceFirst(menu.getPath(), ""),
                                entry.getValue(),
                                menuResourceList.contains(entry.getKey()) && contains)
                        ).toList();
            }

            return new MenuResourceVO(
                    menu.getId(),
                    menu.getPath(),
                    menu.getName(),
                    menu.getIcon(),
                    menu.getParentId(),
                    menu.getTreeLinkId(),
                    contains,
                    resourceList);
        }).toList();

        return buildTreeMenu(menus);
    }

    /**
     * 构建菜单树
     *
     * @param menuList
     * @return
     */
    private List<MenuResourceVO> buildTreeMenu(List<MenuResourceVO> menuList) {
        //根据 parent_id 分组
        Map<Long, List<MenuResourceVO>> groupByParentIdMap = menuList.stream().collect(Collectors.groupingBy(MenuResourceVO::getMenuParentId));

        //构建子菜单
        List<MenuResourceVO> allChildMenus = new ArrayList<>();
        menuList.forEach(menu -> {
            List<MenuResourceVO> children = groupByParentIdMap.get(menu.getMenuId());
            if (children != null) {
                menu.setChildren(children);
                allChildMenus.addAll(children);
            }
        });

        //顶级菜单
        return menuList.stream()
                .filter(menu -> !allChildMenus.contains(menu))
                .filter(menu -> ApiConst.SUPER_PARENT_ID.equals(menu.getMenuParentId()))
                .toList();
    }

    @Override
    public Mono<String> updateMenuResource(List<RoleMenuResourceDTO> param) {
        return Mono.justOrEmpty(param.stream().collect(Collectors.groupingBy(RoleMenuResourceDTO::getSelected)))
                .flatMap(dataMap -> Mono.zip(
                                roleMenuResourceService.insert(dataMap.getOrDefault(true, Collections.emptyList())).subscribeOn(Schedulers.parallel()),
                                roleMenuResourceService.delete(dataMap.getOrDefault(false, Collections.emptyList())).subscribeOn(Schedulers.parallel())
                        )
                        .thenReturn(UPDATE_SUCCESS)
                        .as(transactionalOperator::transactional)
                        .onErrorResume(err -> Mono.error(new CustomException(500, err.getMessage()))));
    }
}
